In [3]:
!mkdir -p ~/agave/funwave-tvd-docker-automation

%cd ~/agave

!pip3 install --upgrade setvar

import re
import os
import sys
from setvar import *
from time import sleep

# This cell enables inline plotting in the notebook
%matplotlib inline

import matplotlib
import numpy as np
import matplotlib.pyplot as plt
!auth-tokens-refresh


/home/jovyan/agave
Requirement already up-to-date: setvar in /opt/conda/lib/python3.6/site-packages
Token for agave.prod:training003 successfully refreshed and cached for 14400 seconds
3ab74b5d74f8c336f108416e1b3c9e9

Automating image building

We saw in the last notebook how we can build images of our funwave-tvd code and use Agave to make the process a bit easier. We can take some lessons learned from the devops community to automate the building of our images and implement basic benchmarking and testing.

While the Agave fork app we created is handy, it doesn't provide particularly good visibility, let along security. We certainly do not want to share an app like that for others to use. So, let's start start by creating a new Agave app that will build our Docker container. First up, our updated app assets.

Creating more meaningful Dockerfiles

While functional, our previous Dockerfile didn't give us much info we could use for things like attribution, discovery, etc. Let's add in some additional fields to give our Dockerfile meaning. We will use a couple new Dockerfile directives to do this

ARG a runtime argument supplied to the docker build command

LABEL one or more terms applied to the image as metadata


In [15]:
writefile("funwave-tvd-docker-automation/Dockerfile","""
FROM stevenrbrandt/science-base
MAINTAINER Steven R. Brandt <sbrandt@cct.lsu.edu>

ARG BUILD_DATE
ARG VERSION

LABEL org.agaveplatform.ax.architecture="x86_64"                                \
      org.agaveplatform.ax.build-date="\$BUILD_DATE"                             \
      org.agaveplatform.ax.version="\$VERSION"                             \
      org.agaveplatform.ax.name="${AGAVE_USERNAME}/funwave-tvd"    \
      org.agaveplatform.ax.summary="Funwave-TVD is a code to simulate the shallow water and Boussinesq equations written by Dr. Fengyan Shi." \
      org.agaveplatform.ax.vcs-type="git"                                       \
      org.agaveplatform.ax.vcs-url="https://github.com/fengyanshi/FUNWAVE-TVD" \
      org.agaveplatform.ax.license="BSD 3-clause"
      
USER root
RUN mkdir -p /home/install
RUN chown jovyan /home/install
USER jovyan

RUN cd /home/install && \
    git clone https://github.com/fengyanshi/FUNWAVE-TVD && \
    cd FUNWAVE-TVD/src && \
    perl -p -i -e 's/FLAG_8 = -DCOUPLING/#$&/' Makefile && \
    make

WORKDIR /home/install/FUNWAVE-TVD/src
RUN mkdir -p /home/jovyan/rundir
WORKDIR /home/jovyan/rundir
""")


Writing file `funwave-tvd-docker-automation/Dockerfile'

Single purpose wrapper scripts

In our previous wrapper script, we simply took whatever was given to us an ran it. Here we will restrict the wrapper to run a specific build comman.

Note that we mix an match a couple variable types. The Agave_* variable are template variables resolved by Agave at runtime with the vales from the job etails. The version variable is a parameter we will define in our app description.


In [62]:
writefile("funwave-tvd-docker-automation/funwave-build-wrapper.txt","""

sudo docker build \
    --build-arg "BUILD_DATE=\${AGAVE_JOB_SUBMIT_TIME}" \
    --build-arg "VERSION=\${code_version}" \
    --rm -t funwave-tvd:\${code_version} .

docker inspect funwave-tvd:\${code_version}

""")


Writing file `funwave-tvd-docker-automation/funwave-build-wrapper.txt'

More descriptive apps

Now we need to create some JSON to tell Agave how to run and advertise our app. This app definition will look a lot like the fork app definition with a few changes. First, we are updating the app id so a new app will be created. Second we change the parameter.

  • code_version is a string parameter describing the version of the code.

We have also removed the data file input from the previous app description. This is because our deployment folder contains the Dockerfile to build our image. No other info is needed to run our build app.


In [63]:
writefile("funwave-tvd-docker-automation/funwave-build-app.txt","""
{  
   "name":"${AGAVE_USERNAME}-${MACHINE_NAME}-funwave-dbuild",
   "version":"1.0",
   "label":"Builds the funwave docker image",
   "shortDescription":"Funwave docker build",
   "longDescription":"",
   "deploymentSystem":"${AGAVE_STORAGE_SYSTEM_ID}",
   "deploymentPath":"automation/funwave-tvd-docker-automation",
   "templatePath":"funwave-build-wrapper.txt",
   "testPath":"test.txt",
   "executionSystem":"${AGAVE_EXECUTION_SYSTEM_ID}",
   "executionType":"CLI",
   "parallelism":"SERIAL",
   "modules":[],
   "inputs":[],
   "parameters":[{
     "id" : "code_version",
     "value" : {
       "visible":true,
       "required":true,
       "type":"string",
       "order":0,
       "enquote":false,
       "default":"latest"
     },
     "details":{
         "label": "Version of the code",
         "description": "If true, output will be packed and compressed",
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     },
     "semantics":{
         "argument": null,
         "showArgument": false,
         "repeatArgument": false
     }
   }],
   "outputs":[]
}
""")


Writing file `funwave-tvd-docker-automation/funwave-build-app.txt'

Here is our default test file


In [64]:
writefile("funwave-tvd-docker-automation/test.txt","""
code_version=latest
""")


Writing file `funwave-tvd-docker-automation/test.txt'

In [65]:
!files-mkdir -S ${AGAVE_STORAGE_SYSTEM_ID} -N automation
!files-upload -S ${AGAVE_STORAGE_SYSTEM_ID} -F funwave-tvd-docker-automation automation


Successfully created folder automation
Creating directory automation/funwave-tvd-docker-automation ...
Uploading funwave-tvd-docker-automation/Dockerfile...
######################################################################## 100.0%
Uploading funwave-tvd-docker-automation/funwave-build-app.txt...
######################################################################## 100.0%
Uploading funwave-tvd-docker-automation/funwave-build-wrapper.txt...
######################################################################## 100.0%
Uploading funwave-tvd-docker-automation/job.json...
######################################################################## 100.0%
Uploading funwave-tvd-docker-automation/test.txt...
######################################################################## 100.0%

In [51]:
!apps-addupdate -F funwave-tvd-docker-automation/funwave-build-app.txt


Successfully added app training003-sandbox-funwave-dbuild-1.0

Running the build


In [52]:
requestbin_url = !requestbin-create
os.environ['REQUESTBIN_URL'] = requestbin_url[0]
setvar("""
WEBHOOK_URL=${REQUESTBIN_URL}
""")


WEBHOOK_URL=https://requestbin.agaveapi.co/126tsd91

Now we'll run our build using the following job request. This is very similar to before.


In [53]:
writefile("funwave-tvd-docker-automation/job.json","""
 {
   "name":"funwave-build",
   "appId": "${AGAVE_USERNAME}-${MACHINE_NAME}-funwave-dbuild-1.0",
   "maxRunTime":"00:10:00",
   "archive": false,
   "notifications": [
    {
      "url":"${WEBHOOK_URL}",
      "event":"*",
      "persistent":"true"
    }
   ],
   "parameters": {
     "code_version":"latest"
   }
 }
""")


Writing file `funwave-tvd-docker-automation/job.json'

Because the setvar() command can evalute $() style bash shell substitutions, we will use it to submit our job. This will capture the output of the submit command, and allow us to parse it for the JOB_ID. We'll use the JOB_ID in several subsequent steps.


In [69]:
setvar("""
# Capture the output of the job submit command
OUTPUT=$(jobs-submit -F funwave-tvd-docker-automation/job.json)
# Parse out the job id from the output
JOB_ID=$(echo $OUTPUT | cut -d' ' -f4)
""")


OUTPUT=Successfully submitted job 7446628594819600871-242ac11b-0001-007
JOB_ID=7446628594819600871-242ac11b-0001-007

In [70]:
for iter in range(20):
    setvar("STAT=$(jobs-status $JOB_ID)")
    stat = os.environ["STAT"]
    sleep(5.0)
    if stat == "FINISHED" or stat == "FAILED":
        break


STAT=PENDING
STAT=PENDING
STAT=PENDING
STAT=STAGED
STAT=SUBMITTING
STAT=SUBMITTING
STAT=RUNNING
STAT=RUNNING
STAT=RUNNING
STAT=RUNNING
STAT=RUNNING
STAT=RUNNING
STAT=RUNNING
STAT=FINISHED

In [74]:
!jobs-output-get -P ${JOB_ID} funwave-build.out


Step 1/13 : FROM stevenrbrandt/science-base
 ---> e93db47f971c
Step 2/13 : MAINTAINER Steven R. Brandt <sbrandt@cct.lsu.edu>
 ---> Using cache
 ---> 13f7ea48e9cb
Step 3/13 : ARG BUILD_DATE
 ---> Using cache
 ---> 548f7a1528d7
Step 4/13 : ARG VERSION
 ---> Using cache
 ---> fdf915eadde3
Step 5/13 : LABEL org.agaveplatform.ax.architecture "x86_64" org.agaveplatform.ax.build-date "$BUILD_DATE" org.agaveplatform.ax.version "$VERSION" org.agaveplatform.ax.name "training003/funwave-tvd" org.agaveplatform.ax.summary "Funwave-TVD is a code to simulate the shallow water and Boussinesq equations written by Dr. Fengyan Shi." org.agaveplatform.ax.vcs-type "git" org.agaveplatform.ax.vcs-url "https://github.com/fengyanshi/FUNWAVE-TVD" org.agaveplatform.ax.license "BSD 3-clause"
 ---> Running in 96e93ee95458
 ---> d68fc3ac3cb6
Removing intermediate container 96e93ee95458
Step 6/13 : USER root
 ---> Running in bbd4a5e10730
 ---> 70866cf2dc8c
Removing intermediate container bbd4a5e10730
Step 7/13 : RUN mkdir -p /home/install
 ---> Running in 28094381b957
 ---> 19e36b026cc3
Removing intermediate container 28094381b957
Step 8/13 : RUN chown jovyan /home/install
 ---> Running in 38c928b7b45e
 ---> f8a893036dc5
Removing intermediate container 38c928b7b45e
Step 9/13 : USER jovyan
 ---> Running in 25180bf3086c
 ---> 13c32aac945e
Removing intermediate container 25180bf3086c
Step 10/13 : RUN cd /home/install &&     git clone https://github.com/fengyanshi/FUNWAVE-TVD &&     cd FUNWAVE-TVD/src &&     perl -p -i -e 's/FLAG_8 = -DCOUPLING/#$&/' Makefile &&     make
 ---> Running in 57c1021d18a0
Cloning into 'FUNWAVE-TVD'...
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_param.F > mod_param.f90
mpif90  -c     mod_param.f90
/bin/rm mod_param.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_global.F > mod_global.f90
mpif90  -c     mod_global.f90
/bin/rm mod_global.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_input.F > mod_input.f90
mpif90  -c     mod_input.f90
/bin/rm mod_input.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_vessel.F > mod_vessel.f90
mpif90  -c     mod_vessel.f90
/bin/rm mod_vessel.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_bathy_correction.F > mod_bathy_correction.f90
mpif90  -c     mod_bathy_correction.f90
/bin/rm mod_bathy_correction.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_meteo.F > mod_meteo.f90
mpif90  -c     mod_meteo.f90
/bin/rm mod_meteo.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mod_parallel_field_io.F > mod_parallel_field_io.f90
mpif90  -c     mod_parallel_field_io.f90
/bin/rm mod_parallel_field_io.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     main.F > main.f90
mpif90  -c     main.f90
/bin/rm main.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     bc.F > bc.f90
mpif90  -c     bc.f90
/bin/rm bc.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     fluxes.F > fluxes.f90
mpif90  -c     fluxes.f90
/bin/rm fluxes.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     init.F > init.f90
mpif90  -c     init.f90
/bin/rm init.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     io.F > io.f90
mpif90  -c     io.f90
/bin/rm io.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     tridiagnal.F > tridiagnal.f90
mpif90  -c     tridiagnal.f90
/bin/rm tridiagnal.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     breaker.F > breaker.f90
mpif90  -c     breaker.f90
/bin/rm breaker.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     derivatives.F > derivatives.f90
mpif90  -c     derivatives.f90
/bin/rm derivatives.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     dispersion.F > dispersion.f90
mpif90  -c     dispersion.f90
/bin/rm dispersion.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     etauv_solver.F > etauv_solver.f90
mpif90  -c     etauv_solver.f90
/bin/rm etauv_solver.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     sponge.F > sponge.f90
mpif90  -c     sponge.f90
/bin/rm sponge.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     sources.F > sources.f90
mpif90  -c     sources.f90
/bin/rm sources.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     masks.F > masks.f90
mpif90  -c     masks.f90
/bin/rm masks.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     parallel.F > parallel.f90
mpif90  -c     parallel.f90
/bin/rm parallel.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     statistics.F > statistics.f90
mpif90  -c     statistics.f90
/bin/rm statistics.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     wavemaker.F > wavemaker.f90
mpif90  -c     wavemaker.f90
/bin/rm wavemaker.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     mixing.F > mixing.f90
mpif90  -c     mixing.f90
/bin/rm mixing.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     nesting.F > nesting.f90
mpif90  -c     nesting.f90
/bin/rm nesting.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     misc.F > misc.f90
mpif90  -c     misc.f90
/bin/rm misc.f90
/usr/bin/cpp  -P -traditional  -P -traditional  -DDOUBLE_PRECISION  -DPARALLEL  -DCARTESIAN                     samples.F > samples.f90
mpif90  -c     samples.f90
/bin/rm samples.f90
mpif90     -o funwave_vessel mod_param.o mod_global.o mod_input.o mod_vessel.o mod_bathy_correction.o mod_meteo.o mod_parallel_field_io.o main.o bc.o fluxes.o init.o io.o tridiagnal.o breaker.o derivatives.o dispersion.o etauv_solver.o sponge.o sources.o masks.o parallel.o statistics.o wavemaker.o mixing.o nesting.o misc.o samples.o 
 ---> e9aeea2d9be8
Removing intermediate container 57c1021d18a0
Step 11/13 : WORKDIR /home/install/FUNWAVE-TVD/src
 ---> c080c05c2288
Removing intermediate container 89912920df55
Step 12/13 : RUN mkdir -p /home/jovyan/rundir
 ---> Running in a4ba7803d059
 ---> 48c6797449d5
Removing intermediate container a4ba7803d059
Step 13/13 : WORKDIR /home/jovyan/rundir
 ---> 3aec8b3a59ef
Removing intermediate container 068c2f8d1901
Successfully built 3aec8b3a59ef
Successfully tagged funwave-tvd:latest
[]

To rebuild our Docker image, we can rerun our job submission command, or simply resumbit the previous job.


In [75]:
setvar("""
# Capture the output of the job submit command
OUTPUT=$(jobs-resubmit ${JOB_ID})
# Parse out the job id from the output
JOB_ID=$(echo $OUTPUT | cut -d' ' -f4)
""")


OUTPUT=Successfully resubmitted job 7446628594819600871-242ac11b-0001-007 as 495140868949470745-242ac11b-0001-007
JOB_ID=7446628594819600871-242ac11b-0001-007

Adding in automation

The reason we go through the trouble of defining a build app, is so we can use it as a tool in our automation process. We can create a simple integration to receive webhooks from github to run our build job on every commit.

Benchmarking

It's strightforward to create another application to run automated benchmarks when our build completes. Taking our previous for execution, let's look at how we can create a customized benchmarking and/or more descriptive app in the next notebook.


In [ ]: